
#include <string.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/socket.h>

#include "../common/log.h"
#include "../common/util.h"
#include "eventloop.h"

#define ADD_TO_EPOLL()                                                         \
    auto it = m_listen_fds.find(event->getFd());                               \
    int op = EPOLL_CTL_ADD;                                                    \
    if (it != m_listen_fds.end()) {                                            \
        op = EPOLL_CTL_MOD;                                                    \
        INFOLOG("change ctl to modify.");                                      \
    }                                                                          \
    epoll_event tmp = event->getEpollEvent();                                  \
    int rt = epoll_ctl(m_epoll_fd, op, event->getFd(), &tmp);                  \
    if (rt == -1) {                                                            \
        ERRORLOG(                                                              \
            "Failed to epoll_ctl when add fd %d, error no = %d, error = %s",   \
            event->getFd(), errno, strerror(errno));                           \
    }                                                                          \
    m_listen_fds.insert(event->getFd());                                       \
    DEBUGLOG("add event success");

#define DELETE_FROM_EPOLL()                                                    \
    auto it = m_listen_fds.find(event->getFd());                               \
    if (it == m_listen_fds.end()) {                                            \
        return;                                                                \
    }                                                                          \
    int op = EPOLL_CTL_DEL;                                                    \
    epoll_event tmp = event->getEpollEvent();                                  \
    int rt = epoll_ctl(m_epoll_fd, op, event->getFd(), &tmp);                  \
    if (rt == -1) {                                                            \
        ERRORLOG("Failed to epoll_ctl when delete fd %d, error no = %d",       \
                 event->getFd(), errno);                                       \
    }                                                                          \
    m_listen_fds.erase(event->getFd());                                        \
    DEBUGLOG("delete event success, fd[%d]", event->getFd());

namespace rocket {

static thread_local EventLoop *t_current_eventloop = nullptr;
static int g_epoll_max_timeout = 10000;
static int g_epoll_max_events = 10;

EventLoop::EventLoop() {
    if (t_current_eventloop != nullptr) {
        ERRORLOG("Failed to create event loop, this thread has created an "
                 "event loop already.");
        exit(0);
    }

    m_thread_id = getThreadId();
    m_epoll_fd = epoll_create(10);
    if (m_epoll_fd == -1) {
        ERRORLOG("Failed to create event loop, epoll_create error, error "
                 "number [%d]",
                 errno);
        exit(0);
    }

    initWakeUpFdEvent();

    initTimer();

    t_current_eventloop = this;
    INFOLOG("Succ create event loop in thead %d", m_thread_id);
}

EventLoop::~EventLoop() {
    close(m_epoll_fd);
    if (m_wakeup_fd_event) {
        delete m_wakeup_fd_event;
        m_wakeup_fd_event = nullptr;
    }
    if (m_timer) {
        delete m_timer;
        m_timer = nullptr;
    }
}

void EventLoop::initWakeUpFdEvent() {

    m_wakeup_fd = eventfd(0, EFD_NONBLOCK);
    if (m_wakeup_fd < 0) {
        ERRORLOG("Failed to create event loop, eventfd create error, error "
                 "number [%d]",
                 errno);
        exit(0);
    }

    INFOLOG("wakeup fd = %d", m_wakeup_fd);

    m_wakeup_fd_event = new WakeUpFdEvent(m_wakeup_fd);
    m_wakeup_fd_event->listen(FdEvent::IN_EVENT, [this]() {
        char buf[8];
        while (read(m_wakeup_fd, buf, 8) != -1 && errno != EAGAIN) {
        }
        DEBUGLOG("read full bytes from wakeup fd[%d]", m_wakeup_fd);
    });

    addEpollEvent(m_wakeup_fd_event);
}

void EventLoop::initTimer() {
    m_timer = new Timer();
    addEpollEvent(m_timer);
}

void EventLoop::addTimeEvent(TimerEvent::s_ptr event) {
    m_timer->addTimerEvent(event);
}

void EventLoop::loop() {
    m_is_looping = true;
    while (!m_stop_flag) {
        ScopeMutex<Mutex> lock(m_mutex);
        std::queue<std::function<void()>> tmp_tasks;
        m_pending_tasks.swap(tmp_tasks);
        lock.unlock();

        while (!tmp_tasks.empty()) {
            std::function<void()> cb = tmp_tasks.front();
            tmp_tasks.pop();
            if (cb) {
                cb();
            }
        }

        int timeout = g_epoll_max_timeout;
        epoll_event result_events[g_epoll_max_events];
        DEBUGLOG("Begin epoll wait");
        int rt =
            epoll_wait(m_epoll_fd, result_events, g_epoll_max_events, timeout);
        DEBUGLOG("Epoll wait end, rt = %d", rt);

        if (rt < 0) {
            ERRORLOG("epoll_wait error, error no = %d", errno);
        } else {
            for (int i = 0; i < rt; i++) {
                epoll_event trigger_event = result_events[i];
                FdEvent *fd_event =
                    static_cast<FdEvent *>(trigger_event.data.ptr);
                if (fd_event == nullptr) {
                    continue;
                }
                if (trigger_event.events & EPOLLIN) {
                    DEBUGLOG("fd %d trigger EPOLLIN event", fd_event->getFd());
                    addTask(fd_event->handler(FdEvent::IN_EVENT), false);
                }
                if (trigger_event.events & EPOLLOUT) {
                    addTask(fd_event->handler(FdEvent::OUT_EVENT), false);
                    DEBUGLOG("fd %d trigger EPOLLOUT event", fd_event->getFd());
                }
                if (trigger_event.events & EPOLLERR) {
                    if (fd_event->handler(FdEvent::ERR_EVENT)) {
                        addTask(fd_event->handler(FdEvent::ERR_EVENT), false);
                    }
                    DEBUGLOG("fd %d trigger EPOLLERR event", fd_event->getFd());
                    deleteEpollEvent(fd_event);
                }
            }
        }
    }
}

void EventLoop::wakeup() {
    INFOLOG("WAKE UP");
    m_wakeup_fd_event->wakeup();
}

void EventLoop::stop() {
    m_stop_flag = true;
    wakeup();
}

void EventLoop::dealWakeup() {}

void EventLoop::addEpollEvent(FdEvent *event) {
    if (isInLoopThread()) {
        ADD_TO_EPOLL();
    } else {
        auto cb = [this, event] { ADD_TO_EPOLL(); };
        addTask(cb, true);
    }
}

void EventLoop::deleteEpollEvent(FdEvent *event) {
    if (isInLoopThread()) {
        DELETE_FROM_EPOLL();
    } else {
        auto cb = [this, event] { DELETE_FROM_EPOLL(); };
        addTask(cb, true);
    }
}

void EventLoop::addTask(std::function<void()> cb, bool is_wake_up) {
    ScopeMutex<Mutex> lock(m_mutex);
    m_pending_tasks.push(cb);
    lock.unlock();

    if (is_wake_up) {
        wakeup();
    }
}

bool EventLoop::isInLoopThread() { return getThreadId() == m_thread_id; }

EventLoop *EventLoop::GetCurrentEventLoop() {
    if (t_current_eventloop) {
        return t_current_eventloop;
    }

    t_current_eventloop = new EventLoop();
    return t_current_eventloop;
}

} // namespace rocket
